import math
import time
import random
import copy
from decimal import Decimal
PI = 3.1415926
NUMIND = 25 # The number of individuals
FUN = 1
crossPro = 0.9
mutaPro = 0.1

class Individual:
    def __init__(self):
        self.gene = [] # Binary gene string
        self.fitness = 0.0 # Fitness value
        self.x = 0.0 # Decimal value
        self.upLim = 0.0 # Upper limit of x value


class Population:
    def __init__(self):
        self.indv = [Individual() for _ in range(NUMIND)]
        self.bestInd = Individual() # Best individual in the population
        self.generan = 0 # Generation number

class Operation:
    def __init__(self):
        self.pop = Population()

    def ranF(self,x, y):
        return random.uniform(x, y)

    def fit(self,x, i):
        """
        The fitness function can return the value of the function f(x).
        Where x is a double number and the return value is the f(x).
        """
        if i == 1:
            return x * math.sin(10 * PI * x) + 2.0
        else:
            return x

    def fit2(self,x1, x2,i):
        """
        The fitness function can return the value of the function f(x).
        Where x is a double number and the return value is the f(x).
        """
        if i == 1:
            return 3 * (x1 ** 2 - x2 ** 2)
        else:
            pass


    def bin2dou(self,bp,L):
        """
        Change the binary string to decimal type
        """
        LENIND = L
        retuV = 0
        for i in range(LENIND):
            tem = int(bp[i])
            retuV += tem * 2 ** (LENIND - i - 1)
        return retuV


    def dou2ZD(self,x, lowBef, upBef, lowAft, upAft):
        return lowAft + (x - lowBef) * (upAft - lowAft) / (upBef - lowBef)

    def decimal_to_binary(self,decimal, L):
        # 将10进制数转换为定长的二进制字符串
        binary = bin(decimal)[2:]
        return binary.zfill(L)

    # 对此处进行修改，初始化为实数编码
    def initPop(self,L):
        """
        Initialize the population,对个体进行循环，对个体中的每一个基因进行赋值。
        """
        LENIND = L
        for i in range(NUMIND):
            indv = Individual()
            # indv.gene = ''.join(['1' if random.random() > 0.5 else '0' for _ in range(LENIND)])
            indv.gene = ['1' if random.random() > 0.5 else '0' for _ in range(LENIND)]
            # 这里将实数映射到-1，2是依据具体问题而定的，所以我们后面的改进型直接在-1，2的范围内进行生成即可。
            indv.x = self.dou2ZD(self.bin2dou(indv.gene,L), 0, 2 ** LENIND - 1, -1, 2)
            indv.upLim = 2.0
            self.pop.indv[i] = indv


    def calFit(self,L):
        LENIND = L
        for i in range(NUMIND):
            # print(self.bin2dou(self.pop.indv[i].gene,L))
            # 当前长度的基因序列，转换到（-1，2）的实数中，
            self.pop.indv[i].x = self.dou2ZD(self.bin2dou(self.pop.indv[i].gene,L), 0, pow(2, LENIND)-1, -1, 2)
            # print(self.pop.indv[i].x)
            # 利用self.pop.indv[i].x得到下一个的基因序列，还是要利用到下一位变长的长度。
            # 将由当前长度的基因序列转换到（-1，2）的实数，。
            temp = int(self.dou2ZD(self.pop.indv[i].x, -1, 2,0, 2 ** LENIND - 1))
            # print(temp)
            # 10进制temp映射到下一位变长长度的二进制上
            self.pop.indv[i].gene = list(self.decimal_to_binary(temp,L))
            # print(type(self.pop.indv[i].gene))
            # print(self.pop.indv[i].gene)
            self.pop.indv[i].fitness = self.fit(self.pop.indv[i].x, FUN)

    def calUp(self):
        sum = 0.0
        for i in range(NUMIND):
            sum += self.pop.indv[i].fitness
        # 第一个个体的上限值等于其适应度除以总适应度（将每个个体的适应度值除以总适应度值，得到每个个体被选中的概率）
        self.pop.indv[0].upLim = self.pop.indv[0].fitness / sum
        for i in range(NUMIND - 1):
            # 其他个体的上限值等于前一个个体的上限值加上其适应度除以总适应度。
            temp = self.pop.indv[i+1].fitness / sum
            self.pop.indv[i+1].upLim = temp + self.pop.indv[i].upLim
            # # 如果某个个体的上限值超过了1.0，并且不是最后一个个体，则会触发一个错误处理机制
            if self.pop.indv[i+1].upLim > 1.0 and (i+1) < (NUMIND-1):
                for j in range(NUMIND):
                    print("generation is: %d, fitness=%f, UpLim=%f" % (
                    self.pop.generan, self.pop.indv[j].fitness, self.pop.indv[j].upLim))
                print("\nError, %d's upLim is greater than 1=============\n" % (i + 1))
        # 大于1对该个体进行惩罚将其上限值设为1
        self.pop.indv[NUMIND - 1].upLim = 1

    def calSub(self):
        # 这样做的目的是为了避免对原有种群进行修改，从而保证计算适应度值的过程不会影响到原有种群。
        self.poptem = Population()
        # 这里模拟了轮盘赌，概率
        TemFitn = [indv.upLim for indv in self.pop.indv]
        for i in range(NUMIND):
            # uniform生成的是范围之内的随机浮点数
            rnd = random.uniform(0, 1)
            # 函数返回的下标 mid 对应的元素是第一个大于等于参数 rnd 的元素的下标
            NumTemp = self.HalfSear(rnd, 0, NUMIND, TemFitn)
            # 将选中的作为下一代的父代个体
            self.poptem.indv[i] = copy.deepcopy(self.pop.indv[NumTemp])
        # 将当前种群的最佳个体bestInd赋值给新种群，保留最佳个体的信息
        # 这样做的好处是，新种群在选择、交叉和变异等操作中不会影响到当前种群
        self.poptem.generan = copy.deepcopy(self.pop.generan)
        self.poptem.bestInd = copy.deepcopy(self.pop.bestInd)
        # self.poptem.bestInd.gene = self.pop.bestInd.gene
        # self.poptem.bestInd.fitness = self.pop.bestInd.fitness
        # self.poptem.bestInd.x = self.pop.bestInd.x
        # self.poptem.bestInd.upLim = self.pop.bestInd.upLim
        self.pop = copy.deepcopy(self.poptem)

    def HalfSear(self,value, LowBo, UpBo, InA):
        if LowBo >= UpBo:
            return UpBo
        Mid = (LowBo + UpBo) // 2
        if Mid == 0:
            return 0
        # 这里表示对范围进行的查找
        if value <= InA[Mid] and value > InA[Mid - 1]:
            return Mid
        else:
            if value >= InA[Mid]:
                return self.HalfSear(value, Mid, UpBo, InA)
            else:
                return self.HalfSear(value, LowBo, Mid, InA)

    def select(self):
        self.calUp()
        self.calSub()

    def switchstr(self,s,t):
        # 随机寻找一个分裂点
        LENIND = len(s)
        i = int(self.ranF(1, LENIND-1))
        # s1 = list(s)
        # t1 = list(t)
        for j in range(i, LENIND):
            temp = s[j]
            s[j] = t[j]
            t[j] = temp

    def changeM(self,ind,L):
        LENIND = L
        r = random.randint(0, LENIND-1)
        ind1 = list(ind)
        if ind1[r] == '0':
            ind1[r] = '1'
        else:
            ind1[r] = '0'
    # 单点变异
    def mutation(self,L):
        for i in range(NUMIND):
            if random.random() > mutaPro:
                continue
            else:
                self.changeM(self.pop.indv[i].gene,L)

    # 两点变异
    def mutation2(self):
        pass

    def max(self):
        j = 0
        tem = self.pop.indv[0].fitness
        for i in range(1, NUMIND):
            if self.pop.indv[i].fitness > tem:
                tem = self.pop.indv[i].fitness
                j = i
        self.pop.bestInd = copy.deepcopy(self.pop.indv[j])

    # 加上一个近亲规避，产生和父代一样的则舍弃
    def crossover(self):
        # print(self.pop.indv[1].gene, self.pop.indv[2].gene)
        # self.switchstr(self.pop.indv[1].gene, self.pop.indv[2].gene)
        # print(self.pop.indv[1].gene, self.pop.indv[2].gene)
        # 表示随机选择的次数，每次选择两个
        for i in range(NUMIND//2):
            if random.random() > crossPro:
                continue
            else:
                # 随机选择两个个体进行交叉,这里代表个体的序号
                j = random.randint(0, NUMIND-1)
                random.random()
                k = random.randint(0, NUMIND-1)
                # print(self.pop.indv[j].gene, self.pop.indv[k].gene)
                self.switchstr(self.pop.indv[j].gene, self.pop.indv[k].gene)
                # print(self.pop.indv[j].gene, self.pop.indv[k].gene)



if __name__ == '__main__':
    # file = open('result.txt','w')
    # # global mutaPro
    fp = open("a.txt", "w")
    random.seed(int(time.time()) % 9)
    operate = Operation()
    operate.initPop(5)

    while operate.pop.bestInd.fitness <= 3.85027 and operate.pop.generan < 10000:
        # if operate.pop.generan <= 50:
        L = int(5 + 0.1 * operate.pop.generan)
        if L >= 308:
            break
        operate.initPop(L)
        # elif 50 < operate.pop.generan < 90:
        #     L = 10
        #     operate.initPop(L)
        # else:
        #     L = int(10 + (100 - operate.pop.generan) ** 2)
        #     print(L)
        #     if L >= 308:
        #         break
        #     operate.initPop(L)

        operate.calFit(L)
        operate.max()
        print("The generation is %d, the best fitness is %f" % (operate.pop.generan, operate.pop.bestInd.fitness))
        fp.write("Best Individual: %s, %f\n" % (operate.pop.bestInd.gene, operate.pop.bestInd.fitness))
        operate.select()
        operate.crossover()
        print("after crossover")
        if operate.pop.generan == 100:
            mutaPro = 0.3
            print("The mutation probability is set to 0.3")
        operate.mutation(L)
        operate.pop.indv[0] = operate.pop.bestInd
        operate.pop.generan += 1
    print("finished")
    fp.close()